package com.threeti.mecool.core.util;

import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.dozer.Mapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.repository.CrudRepository;
import org.springframework.util.CollectionUtils;

import com.threeti.mecool.framework.util.Lists;
import com.threeti.mecool.shared.util.AbstractFlatRawTreeNode;
import com.threeti.mecool.shared.util.AbstractTreeNode;

/** @author Jay Meng */
public abstract class TreeHelper {//TODO jay:upcast to compatible with TreeNode
  private static final Logger logger = LoggerFactory.getLogger(TreeHelper.class);
  
  public static final <T extends AbstractTreeNode<TK>, TK extends Serializable>List<T> templateBuildTree(
      List<? extends AbstractFlatRawTreeNode<TK>> flatRawTree, 
      Class resultRichTreeNodeClazz,
      Map<TK, String> namePreFetchedRegistry) {
    return templateBuildTree(flatRawTree, resultRichTreeNodeClazz, null, null, namePreFetchedRegistry);
  }
  
  public static final <T extends AbstractTreeNode<TK>, TK extends Serializable>List<T> templateBuildTree(
      List<? extends AbstractFlatRawTreeNode<TK>> flatRawTree, 
      Class resultRichTreeNodeClazz, 
      CrudRepository rawTreeNodeRepo) {
    return templateBuildTree(flatRawTree, resultRichTreeNodeClazz, rawTreeNodeRepo, null, null);
  }

  public static final <T extends AbstractTreeNode<TK>, TK extends Serializable>List<T> templateBuildTree(
      List<? extends AbstractFlatRawTreeNode<TK>> flatRawTree, 
      Class resultRichTreeNodeClazz, 
      CrudRepository rawTreeNodeRepo,
      TK relativeSingleRootNodeId) {
    return templateBuildTree(flatRawTree, resultRichTreeNodeClazz, rawTreeNodeRepo, relativeSingleRootNodeId, null);
  }

  private static <T extends AbstractTreeNode<TK>, TK extends Serializable>List<T> templateBuildTree(
      List<? extends AbstractFlatRawTreeNode<TK>> flatRawTree, 
      Class resultRichTreeNodeClazz, 
      CrudRepository rawTreeNodeRepo,
      TK relativeSingleRootNodeId,
      Map<TK, String> namePrefetchedRegistry) {
    //1.build tree's fast image
    Object[] treeFastImages = buildFastImagesOfFlatTree(flatRawTree, relativeSingleRootNodeId);

    Map<TK, LinkedHashSet<TK>> treeFastImage = (Map<TK, LinkedHashSet<TK>>)treeFastImages[0];
    List<TK> topLevelNodeIds = (List<TK>)treeFastImages[1];
    
    //2.through tree's fast image,build part of top level nodes(this kind of top level nodes have childs)
    List<T> resultTopLevelNodes = buildTopLevelTreeNodesWhoHaveChilds(treeFastImage, topLevelNodeIds, resultRichTreeNodeClazz, rawTreeNodeRepo, namePrefetchedRegistry);
    
    logger.debug("treeFastImage: " + treeFastImage);
    
    //3.build rest top level nodes(this kind of top level nodes have no childs)
    buildRestTopLevelTreeNodesWhoHaveNoChilds(topLevelNodeIds, resultTopLevelNodes, resultRichTreeNodeClazz, rawTreeNodeRepo, namePrefetchedRegistry);
    

    logger.debug("topLevelNodeIds(without childs): " + topLevelNodeIds);
    return resultTopLevelNodes;
  }

  private static <TK extends Serializable>Object[] buildFastImagesOfFlatTree(List<? extends AbstractFlatRawTreeNode<TK>> flatRawTree) {    
    return buildFastImagesOfFlatTree(flatRawTree, null);
  }

  private static <TK extends Serializable>Object[] buildFastImagesOfFlatTree(List<? extends AbstractFlatRawTreeNode<TK>> flatRawTree, TK relativeSingleRootNodeId) {
    Map<TK, LinkedHashSet<TK>> treeFastImage = new HashMap<TK, LinkedHashSet<TK>>();
    List<TK> topLevelNodeIds = new ArrayList<TK>();
    boolean isRootNodeSpecified = (relativeSingleRootNodeId != null);
    
    if (isRootNodeSpecified) {
      //only store specified single root
      topLevelNodeIds.add(relativeSingleRootNodeId);
      
      for (AbstractFlatRawTreeNode<TK> node : flatRawTree) {
        TK currentNodeId = node.getId();
        TK currentParentId = node.getParentId();
        
        innerBuildFastImagesOfFlatTree(treeFastImage, currentNodeId, currentParentId);
      }
    } else {
      for (AbstractFlatRawTreeNode<TK> node : flatRawTree) {
        TK currentNodeId = node.getId();
        TK currentParentId = node.getParentId();
        if (currentParentId == null) {
          //store all the top level nodes
          topLevelNodeIds.add(currentNodeId);
        } else {
          innerBuildFastImagesOfFlatTree(treeFastImage, currentNodeId, currentParentId);
        }
      }
    }
    
    return new Object[]{treeFastImage, topLevelNodeIds};
  }

  private static <TK extends Serializable>void innerBuildFastImagesOfFlatTree(Map<TK, LinkedHashSet<TK>> treeFastImage, TK currentNodeId, TK currentParentId) {
    LinkedHashSet<TK> currentChildIds = null;
    if ((currentChildIds = treeFastImage.get(currentParentId)) != null) {
      currentChildIds.add(currentNodeId);//join as another child next to current last child of current parent node
    } else {
      currentChildIds = new LinkedHashSet<TK>();
      currentChildIds.add(currentNodeId);//join as the first child of current parent node
      treeFastImage.put(currentParentId, currentChildIds);
    }
  }

  private static <T extends AbstractTreeNode<TK>,TK extends Serializable> List<T> buildTopLevelTreeNodesWhoHaveChilds(
      Map<TK, LinkedHashSet<TK>> treeFastImage, 
      List<TK> topLevelNodeIds,
      Class resultRichTreeNodeClazz, CrudRepository rawTreeNodeRepo, Map<TK, String> namePrefetchedRegistry) {
    List<T> resultTopLevelNodes = Lists.newArrayList();
    
    Set<Entry<TK, LinkedHashSet<TK>>> treePreImageSets = treeFastImage.entrySet();
    Map<TK, T> treeRegistry = new HashMap<TK, T>();
    for (Entry<TK, LinkedHashSet<TK>> singleDeepParentWithDirectChilds : treePreImageSets) {
      //fetch parent
      TK parentId = singleDeepParentWithDirectChilds.getKey();      
      //get parent from tree registry(or register it self) to see whether it is existed or already be a child of its parent,
      //for the purpose maintaining always the same parent reference
      T parentNode =  buildConnectionWithTheTree(treeRegistry, parentId, resultRichTreeNodeClazz, rawTreeNodeRepo, namePrefetchedRegistry);
      
      //fetch direct childs
      LinkedHashSet<TK> directChildIds = singleDeepParentWithDirectChilds.getValue();
      for (TK directChildId : directChildIds) {        
        //get child from tree registry(or register it self) to see whether it is existed or already be a parent of its child,
        //for the purpose maintaining always the same child reference
        T childNode = buildConnectionWithTheTree(treeRegistry, directChildId, resultRichTreeNodeClazz, rawTreeNodeRepo, namePrefetchedRegistry);
        parentNode.addChild(childNode);
      }
      //as result,build top level nodes(this kind of top level nodes have childs)
      if (topLevelNodeIds.contains(parentId)) {
        resultTopLevelNodes.add(parentNode);
        topLevelNodeIds.remove(parentId);
      }
    }
    
    return resultTopLevelNodes;
  }

  private static <T, TK extends Serializable>void buildRestTopLevelTreeNodesWhoHaveNoChilds(
      List<TK> topLevelNodeIds, List<T> resultTopLevelNodes, 
      Class resultRichTreeNodeClazz, CrudRepository rawTreeNodeRepo, 
      Map<TK, String> namePrefetchedRegistry) {
    T node = null;
    AbstractFlatRawTreeNode<TK> topLevelNodeWithNoChild = null;
    try {
      for (TK topLevelNodeId : topLevelNodeIds) {
        if (rawTreeNodeRepo != null && namePrefetchedRegistry == null) {
          topLevelNodeWithNoChild = (AbstractFlatRawTreeNode<TK>)rawTreeNodeRepo.findOne(topLevelNodeId);

          Constructor[] consts = resultRichTreeNodeClazz.getConstructors();
          for (Constructor con : consts) {
            if (con.getParameterTypes().length == 2){
              
              if (topLevelNodeWithNoChild == null) {
                node = (T)con.newInstance(topLevelNodeId, "系统自动校正值");//TODO jay:假设nodeId不会和现有id重复
              } else {
                node = (T)con.newInstance(topLevelNodeWithNoChild.getId(), topLevelNodeWithNoChild.getName());//TODO jay:cache reflection?
              }
              break;
            }
          }
          
        } else  if (namePrefetchedRegistry != null) {

          Constructor[] consts = resultRichTreeNodeClazz.getConstructors();
          for (Constructor con : consts) {
            if (con.getParameterTypes().length == 2){
              node = (T)con.newInstance(topLevelNodeId, namePrefetchedRegistry.get(topLevelNodeId));
              break;
            }
          }          
          
        } else {
          throw new RuntimeException("创建树失败,未提供足够信息, [rawTreeNodeRepo: " + rawTreeNodeRepo + ", namePrefetchedRegistry: "+namePrefetchedRegistry+"]");
        }
        
        //copy other properties if defined and same with source
        if (topLevelNodeWithNoChild != null) {
          Mapper transfer = DozerMapperSingleton.getInstance();
          transfer.map(topLevelNodeWithNoChild, node);
        }
        
        resultTopLevelNodes.add(node);
      }
    } catch (Exception e) {
      logger.error("创建树失败", e);
      //throw new RuntimeException("创建树失败", e);keep silent
      return;
    }
  }

  private static <T,K extends Serializable> T buildConnectionWithTheTree(Map<K, T> treeRegistry, K nodeId, Class resultRichTreeNodeClazz, CrudRepository repo, Map<K, String> namePrefetchedRegistry) {
    T node;
    AbstractFlatRawTreeNode<K> theExistParentOrChild = null;
    try {
      if ((node = treeRegistry.get(nodeId)) == null) {
        if (repo != null && namePrefetchedRegistry == null) {
          theExistParentOrChild = (AbstractFlatRawTreeNode<K>)repo.findOne(nodeId);
          
          Constructor[] consts = resultRichTreeNodeClazz.getConstructors();
          for (Constructor con : consts) {
            if (con.getParameterTypes().length == 2) {
              if (theExistParentOrChild == null) {
                node = (T)con.newInstance(nodeId, "系统自动校正值");//TODO jay:假设nodeId不会和现有id重复
              } else {
                node = (T)con.newInstance(theExistParentOrChild.getId(), theExistParentOrChild.getName());//TODO jay:cache reflection?
              }
              break;
            }
          }
          
        } else if (namePrefetchedRegistry != null) {
          Constructor[] consts = resultRichTreeNodeClazz.getConstructors();
          for (Constructor con : consts) {
            if (con.getParameterTypes().length == 2){
              node = (T)con.newInstance(nodeId, namePrefetchedRegistry.get(nodeId));
              break;
            }
          }
        } else {
          throw new RuntimeException("创建树失败,未提供足够信息, [repo: " + repo + ", namePrefetchedRegistry: "+namePrefetchedRegistry+"]");
        } 
        
        //copy other properties if defined and same with source
        if (theExistParentOrChild != null) {
          Mapper transfer = DozerMapperSingleton.getInstance();
          transfer.map(theExistParentOrChild, node);
        }
        
        treeRegistry.put(nodeId, node);
      }
      return node;
    } catch (Exception e) {
      logger.error("创建树失败", e);
      //throw new RuntimeException("创建树失败", e);keep silent
      return null;
    }
  }
  
  public static <TK extends Serializable>void dumpTree(AbstractTreeNode<TK> entryNode) {
    
    if (CollectionUtils.isEmpty(entryNode.getChilds())) {
      logger.debug("leaf node:" + entryNode +",with parent:"+(entryNode.getParent()!=null?entryNode.getParent():"null"));
      return;//touch leaf node
    } else {
      for (AbstractTreeNode<TK> childNode : entryNode.getChilds()) {
        logger.debug("normal node:" + childNode +",with parent:"+(childNode.getParent()!=null?childNode.getParent():"null"));
        dumpTree(childNode);
      }
    }    
  }
}
